#include "stdafx.h"
#include "RenderingSystem.h"

#include "GlobalAppData.h"
#include "Mesh.h"
#include "MeshInstance.h"
#include "Frustum.h"

using namespace mgfx;

ParticleSystem * gPartSys = NULL;

RenderingSystem::RenderingSystem()
	: mNextID(INCORRECT_OBJ_ID + 1)
{
	mgfx::MeshDesc meshDesc;
	meshDesc.FileName = "box.ms3d";
	meshDesc.LoadTextures = false;
	Data.BrickMesh = Mesh::createFromFile(meshDesc);
}

RenderingSystem::~RenderingSystem()
{
}

void RenderingSystem::update(float dt)
{
	if(Settings.DisplayIcons)
		for(uint i = 0; i < Data.PartSystems.NumElements; i++)
		{
			Data.PartSystems[i].update(dt);
		}
	for(uint i = 0; i < Data.SpotLights.NumElements; i++)
	{
		Data.SpotLights[i].updateDerivedValues();
	}
}

MeshInstance * RenderingSystem::createMeshInstance(const std::string & filename, uint id)
{
	MeshInstance instance;
	instance.Mesh_ = Mesh::createFromFile(filename);
	instance.System = this;
	Data.Meshes.add(instance);
	addToGfxObjectMap(Data.Meshes.back(), id);
	return &Data.Meshes.back();
}

MeshInstance * mgfx::RenderingSystem::createMeshInstance( const MeshDesc & desc, uint id )
{
	MeshInstance instance;
	instance.Mesh_ = Mesh::createFromFile(desc);
	instance.System = this;
	Data.Meshes.add(instance);
	addToGfxObjectMap(Data.Meshes.back(), id);
	return &Data.Meshes.back();
}

MeshInstance * RenderingSystem::createMeshInstanceCopy( const MeshInstance & src )
{
	Data.Meshes.add(src);
	addToGfxObjectMap(Data.Meshes.back());
	return &Data.Meshes.back();
}

DirectionalLight * RenderingSystem::getMainLight()
{
	return &Data.MainLight;
}
		
PointLight * RenderingSystem::createPointLight(const PointLightDesc & desc, uint id)
{
	PointLight light;
	light.init(desc);
	light.System = this;
	Data.PointLights.add(light);
	addToGfxObjectMap(Data.PointLights.back(), id);
	return &Data.PointLights.back();
}

PointLight * RenderingSystem::createPointLightCopy( const PointLight & src )
{
	Data.PointLights.add(src);
	addToGfxObjectMap(Data.PointLights.back());
	return &Data.PointLights.back();
}

SpotLight * RenderingSystem::createSpotLight(const SpotLightDesc & desc, uint id)
{
	SpotLight light;
	light.init(desc);
	light.System = this;
	Data.SpotLights.add(light);
	addToGfxObjectMap(Data.SpotLights.back(), id);
	return &Data.SpotLights.back();
}

SpotLight * RenderingSystem::createSpotLightCopy( const SpotLight & src )
{
	Data.SpotLights.add(src);
	Data.SpotLights.back().initShadowMap(); // hopefully temporary only (should be more POD type and not require hacks like this while making a copy)
	addToGfxObjectMap(Data.SpotLights.back());
	return &Data.SpotLights.back();
}

ParticleSystem * RenderingSystem::createParticleSystem(const ParticleSystemDesc & desc, uint id)
{
	ParticleSystem sys;
	sys.init(desc);
	sys.System = this;
	Data.PartSystems.add(sys);
	addToGfxObjectMap(Data.PartSystems.back(), id);
	return &Data.PartSystems.back();
}

static void updateViewProjMatrix(RenderingData & data)
{
	XMMATRIX view = XMLoadFloat4x4(&data.ViewMatrix);
	XMMATRIX proj = XMLoadFloat4x4(&data.ProjMatrix);
	XMMATRIX viewProj = XMMatrixMultiply(view, proj);
	XMStoreFloat4x4(&data.ViewProjMatrix, viewProj);
}

void RenderingSystem::setViewMatrix(const XMFLOAT4X4 & view)
{
	Data.ViewMatrix = view;
	updateViewProjMatrix(Data);
}

void RenderingSystem::setProjMatrix(const XMFLOAT4X4 & proj)
{
	Data.ProjMatrix = proj;
	updateViewProjMatrix(Data);
}

void RenderingSystem::setEyePosition(const XMFLOAT3 & eyePos)
{
	Data.EyePosition = eyePos;
}

void RenderingSystem::setViewFrustum(const mmath::FrustumPersp & frustum)
{
	Data.ViewFrustum = frustum;
}

GfxObject & RenderingSystem::findGfxObject(uint id)
{
	assert(mGfxObjects.find(id) != mGfxObjects.end());
	return *mGfxObjects[id];
}

void RenderingSystem::removeGfxObject(GfxObject & obj)
{
	MeshInstance * mesh = dynamic_cast<MeshInstance*>(&obj);
	if(mesh != NULL)
	{
		// if obj to remove not the last object in the list, swap them and update the moved object from las position
		// so it can be found by its users
		MeshInstance & lastObj = Data.Meshes.back();
		if(mesh != &lastObj)
		{
			std::swap(*mesh, lastObj);
			updateGfxObjectMapEntry(*mesh);
		}
		// remove last object's entry from objects map
		removeGfxObjectMapEntry(lastObj);
		Data.Meshes.removeLast();
		return;
	}

	PointLight * pLight = dynamic_cast<PointLight*>(&obj);
	if(pLight != NULL)
	{
		PointLight & lastObj = Data.PointLights.back();
		if(pLight != &lastObj)
		{
			std::swap(*pLight, lastObj);
			updateGfxObjectMapEntry(*pLight);
		}
		removeGfxObjectMapEntry(lastObj);
		Data.PointLights.removeLast();
		return;
	}

	SpotLight * sLight = dynamic_cast<SpotLight*>(&obj);
	if(sLight != NULL)
	{
		SpotLight & lastObj = Data.SpotLights.back();
		if(sLight != &lastObj)
		{
			std::swap(*sLight, lastObj);
			updateGfxObjectMapEntry(*sLight);
		}
		removeGfxObjectMapEntry(lastObj);
		Data.SpotLights.removeLast();
		return;
	}

	ParticleSystem * sPartSys = dynamic_cast<ParticleSystem*>(&obj);
	if(sPartSys != NULL)
	{
		//sPartSys->deinit();
		ParticleSystem & lastObj = Data.PartSystems.back();
		if(sPartSys != &lastObj)
		{
			std::swap(*sPartSys, lastObj);
			updateGfxObjectMapEntry(*sPartSys);
		}
		removeGfxObjectMapEntry(lastObj);
		Data.PartSystems.removeLast();
		return;
	}
}

void RenderingSystem::addToGfxObjectMap(GfxObject & obj, uint id)
{
	if(id == INCORRECT_OBJ_ID)
		id = mNextID++;
	
	obj.Id = id;
	mGfxObjects[id] = &obj;
}

void RenderingSystem::updateGfxObjectMapEntry(GfxObject & obj)
{
	mGfxObjects[obj.Id] = &obj;
}

void RenderingSystem::removeGfxObjectMapEntry(GfxObject & obj)
{
	mGfxObjects.erase(obj.Id);
	obj.Id = INCORRECT_OBJ_ID;
}